package wangwenjun.phase3.atomic;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import sun.misc.Unsafe;

import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static sun.misc.Unsafe.getUnsafe;

/**
 * @author ChangLiang
 * @date 2020/6/20
 */
@Slf4j
public class UnsafeTest {

    @Test
    @DisplayName("test stupid counter")
    public void test() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1000);
        Counter counter = new StupidCounter();
        doTest(executorService, counter);
    }

    @Test
    @DisplayName("test synchronized counter")
    public void test2() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1000);
        Counter counter = new SyncCounter();
        doTest(executorService, counter);
    }

    @Test
    @DisplayName("test lock counter")
    public void test3() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1000);
        Counter counter = new LockCounter();
        doTest(executorService, counter);
    }

    @Test
    @DisplayName("test atomic counter")
    public void test4() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1000);
        Counter counter = new AtomicCounter();
        doTest(executorService, counter);
    }

    @Test
    @DisplayName("test cas unsafe counter")
    public void test5() throws InterruptedException, NoSuchFieldException {
        ExecutorService executorService = Executors.newFixedThreadPool(1000);
        Counter counter = new CasUnsafeCounter();
        doTest(executorService, counter);
    }

    private void doTest(ExecutorService executorService, Counter counter) throws InterruptedException {
        log.warn("------------------------------------");
        long startTime = Instant.now().toEpochMilli();
        for (int i = 0; i < 1000; i++) {
            executorService.submit(new CounterRunnable(counter, 10000));
        }
        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.HOURS);
        long endTime = Instant.now().toEpochMilli();
        log.info("Counter result:{}", counter.getCounter());
        log.info("Time elapsed in {} ms", endTime - startTime);
    }

    @AllArgsConstructor
    static class CounterRunnable implements Runnable{

        private final Counter counter;
        private final int num;

        @Override
        public void run() {
            for (int i = 0; i < num; i++) {
                counter.increment();
            }
        }
    }

    interface Counter{
        void increment();

        long getCounter();
    }

    static class StupidCounter implements Counter {

        private long counter = 0;

        @Override
        public void increment() {
            counter++;
        }

        @Override
        public long getCounter() {
            return counter;
        }
    }

    static class SyncCounter implements Counter {

        private long counter = 0;

        @Override
        public synchronized void increment() {
            counter++;
        }

        @Override
        public long getCounter() {
            return this.counter;
        }
    }

    static class LockCounter implements Counter{

        private long counter = 0;

        private Lock lock = new ReentrantLock();

        @Override
        public void increment() {
            try {
                lock.lock();
                counter++;
            } finally {
                lock.unlock();
            }
        }

        @Override
        public long getCounter() {
            return this.counter;
        }
    }

    static class AtomicCounter implements Counter{

        private AtomicInteger atomicInteger = new AtomicInteger(0);

        @Override
        public void increment() {
            atomicInteger.incrementAndGet();
        }

        @Override
        public long getCounter() {
            return atomicInteger.get();
        }
    }

    static class CasUnsafeCounter implements Counter{

        private volatile long counter = 0;
        private Unsafe unsafe;
        private long offset;

        CasUnsafeCounter() throws NoSuchFieldException {
            unsafe = getUnsafe();
            offset = unsafe.objectFieldOffset(CasUnsafeCounter.class.getDeclaredField("counter"));
        }

        @Override
        public void increment() {
            long current = counter;
            while (!unsafe.compareAndSwapLong(this, offset, current, current + 1)) {
                current = counter;
            }
        }

        @Override
        public long getCounter() {
            return this.counter;
        }
    }
}
